﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Metadata.Reader.API;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Modules;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.Util.Special;

namespace MugenMvvmToolkit.ReSharper
{
    public static class ToolkitExtensions
    {
        #region Fields

        public static readonly HashSet<string> IgnoreProperties = new HashSet<string>
        {
            "IsNotificationsSuspended"
        };

        private static readonly HashSet<string> IgnoreTaskMethods = new HashSet<string>
        {
            "FromResult",
            MethodNameConstants.MvvmExtensionsWithBusyIndicator
        };

        #endregion

        #region Methods

        public static bool IsImplementGenericView([NotNull] IClassDeclaration classDeclaration)
        {
            if (classDeclaration == null) throw new ArgumentNullException("classDeclaration");
            return classDeclaration.IsImplementGenericInterface(InterfaceNameConstants.ViewGeneric);
        }

        [CanBeNull]
        public static IType GetEntityModelType([CanBeNull] IClassLikeDeclaration classDeclaration)
        {
            if (classDeclaration == null)
                return null;
            ITypeElement declaredElement = classDeclaration.DeclaredElement;
            if (declaredElement == null)
                return null;
            IDeclaredType classType = TypeFactory.CreateType(declaredElement);
            var typeProvider = TypeProvider.Create(classDeclaration.GetPsiModule(), classDeclaration.GetResolveContext());
            return GetEntityModelType(classType, typeProvider);
        }

        [CanBeNull]
        public static IType GetEntityModelType([NotNull] IDeclaredType classType, TypeProvider typeProvider)
        {
            if (classType == null) throw new ArgumentNullException("classType");
            if (!classType.IsOfType(typeProvider.EditableViewModelInterface))
                return null;

            IDeclaredType editableViewModelType = classType
                .GetAllSuperTypes()
                .FirstOrDefault(declaredType => declaredType.GetClrName().FullName == ClassNameConstants.EditableViewModel);
            if (editableViewModelType == null)
                return null;
            ISubstitution substitution = editableViewModelType.GetSubstitution();
            return substitution.Apply(substitution.Domain[0]);
        }

        [NotNull]
        public static IEnumerable<IProperty> GetPublicProperties([CanBeNull] this IClass @class)
        {
            if (@class == null)
                return Enumerable.Empty<IProperty>();
            return @class
                .Properties
                .Where(property => !IgnoreProperties.Contains(property.ShortName) &&
                                   property.GetAccessRights() == AccessRights.PUBLIC);
        }

        [NotNull]
        public static IEnumerable<IProperty> GetModelProperties([CanBeNull] IClass modelClass)
        {
            return modelClass.GetPublicProperties();
        }

        [CanBeNull]
        public static Tuple<ICSharpLiteralExpression, IClass, IEnumerable<IProperty>> TryGetValidateMethod(
            [CanBeNull] ICSharpLiteralExpression literalExpression, IPsiModule psiModule,
            IModuleReferenceResolveContext context)
        {
            if (literalExpression == null)
                return null;

            var invokeExpression = literalExpression.FindParent<IInvocationExpression>();
            var method = invokeExpression.GetReferenceDeclaredElement<IMethod>();

            TypeProvider provider = TypeProvider.Create(psiModule, context);
            if (invokeExpression == null || method == null || method.ShortName != MethodNameConstants.ValidatableViewModelInterfaceValidate ||
                method.Parameters.Count != 1 || !method.Parameters[0].Type.IsOfType(provider.String))
                return null;

            IClass classType = null;
            if (invokeExpression.ExtensionQualifier != null)
            {
                var expressionType = invokeExpression.ExtensionQualifier.GetExpressionType() as IDeclaredType;
                if (expressionType == null || !expressionType.IsOfType(provider.ValidatableViewModelInterface))
                    return null;
                classType = expressionType.GetClassType();
            }
            else
            {
                var classLikeDeclaration = invokeExpression.FindParent<IClassLikeDeclaration>();
                if (classLikeDeclaration != null &&
                    classLikeDeclaration.IsOfType(provider.ValidatableViewModelInterface))
                    classType = classLikeDeclaration.DeclaredElement as IClass;
            }
            if (classType == null)
                return null;

            var modelType = GetEntityModelType(TypeFactory.CreateType(classType), provider);
            var properties = classType
                .GetPublicProperties()
                .Concat(GetModelProperties(modelType.IfNotNull(type => type.GetClassType())))
                .Where(property => !property.HasAttributeInstance(TypeProvider.IgnorePropertyAttributeClrName, true));
            return Tuple.Create(literalExpression, classType, properties);
        }

        [CanBeNull]
        public static ICSharpLiteralExpression TryGetModelPropertyAttributeValue(IAttribute attribute, TypeProvider typeProvider)
        {
            if (attribute == null)
                return null;
            if (attribute.Arguments.Count == 0 || !attribute.IsOfType(typeProvider.ModelPropertyAttribute))
                return null;
            return attribute.Arguments[0].Expression as ICSharpLiteralExpression;
        }

        public static bool HasViewModelTypeRequiredAttribute([CanBeNull] ICSharpArgument argument)
        {
            if (argument == null)
                return false;
            var parametersOwner = argument
                .Invocation
                .GetReferenceDeclaredElement<IParametersOwner>();
            var parameterIndex = argument.IndexOf();
            if (parametersOwner == null || parametersOwner.Parameters.Count <= parameterIndex)
                return false;
            return parametersOwner
                .Parameters[parameterIndex]
                .HasAttributeInstance(TypeProvider.ViewModelTypeRequiredAttributeClrName, true);
        }

        public static bool HasShouldBeInitializedAttribute([CanBeNull] IReferenceExpression argument)
        {
            var attributesSet = argument.GetDeclaredElement<IDeclaredElement>() as IAttributesSet;
            return attributesSet != null && attributesSet
                .HasAttributeInstance(TypeProvider.ShouldBeInitializedAttributeClrName, true);
        }

        public static bool HasNotEmptyParamsAttribute([CanBeNull] IAttributesSet argument)
        {
            if (argument == null)
                return false;
            return argument
                .HasAttributeInstance(TypeProvider.NotEmptyParamsAttributeClrName, true);
        }

        public static bool HasTaskBusyHandler(IInvocationExpression invocationExpression, TypeProvider typeProvider)
        {
            var method = invocationExpression.Reference.GetDeclaredElement<IMethod>();
            if (method != null && IgnoreTaskMethods.Contains(method.ShortName))
                return true;
            if (!typeProvider.SuppressTaskBusyHandlerAttribute.IsResolved)
                return false;
            var attributesSet = invocationExpression.Reference.GetDeclaredElement<IDeclaredElement>() as IAttributesSet;
            return HasTaskBusyHandler(attributesSet);
        }

        public static bool HasTaskBusyHandler([CanBeNull]IAttributesSet attributesSet)
        {
            return attributesSet != null && attributesSet.HasAttributeInstance(TypeProvider.SuppressTaskBusyHandlerAttributeClrName, true);
        }

        private static bool IsImplementGenericInterface([NotNull] this IClassLikeDeclaration classDeclaration, [NotNull] string interfaceName)
        {
            if (classDeclaration == null) throw new ArgumentNullException("classDeclaration");
            if (interfaceName == null) throw new ArgumentNullException("interfaceName");
            ITypeElement declaredElement = classDeclaration.DeclaredElement;
            if (declaredElement == null)
                return false;
            IList<IDeclaredType> superTypes = declaredElement.GetSuperTypes();
            return superTypes.Any(type => type.IsGenericClass() && type.GetClrName().FullName == interfaceName);
        }

        #endregion
    }
}
